﻿using System;
using System.Collections.Generic;
using DDDNorthWind.Core.Domain.DomainEvents;
using DDDNorthWind.Core.Domain.Events;
using DDDNorthWind.Core.Domain.Identity;

namespace DDDNorthWind.Core.Domain.Aggregate
{
    public abstract class AggregateRoot<TIdentityKey, TKey> : IEventSuscriptor, IAggregateRoot<TIdentityKey> where TIdentityKey : IdentityKey<TKey>
    {
        protected DateTime? Version = null;
        private readonly Dictionary<Type, List<Action<IDomainEvent>>> routes = new Dictionary<Type, List<Action<IDomainEvent>>>();
        private readonly List<IDomainEvent> appliedEvents = new List<IDomainEvent>();
        protected AggregateRoot(TIdentityKey id)
        {
            Id = id;
            Init();
        }

        protected AggregateRoot(IUniqueIdentifierGenerator<TKey> uniqueIdentifierGenerator)
        {
            Init();
// ReSharper disable DoNotCallOverridableMethodsInConstructor
            CreateNewIdentity(uniqueIdentifierGenerator);
// ReSharper restore DoNotCallOverridableMethodsInConstructor

            

        }
        void Init()
        {
            RegisterAggregateEvents();
        }


        protected abstract void CreateNewIdentity(IUniqueIdentifierGenerator<TKey> uniqueIdentifierGenerator);

        public abstract TIdentityKey Id { get; protected set; }


        #region IEventSuscriptor Members

        public void RegisterEvent<TDomainEvent>(Action<TDomainEvent> handler) where TDomainEvent : IDomainEvent
        {
            var handlers = GetHandlers<TDomainEvent>();
            handlers.Add(DelegateAdjuster.CastArgument<IDomainEvent, TDomainEvent>(x => handler.Invoke(x)));
        }

        public void Raise<TDomainEvent>(TDomainEvent @event) where TDomainEvent : IDomainEvent
        {
            List<Action<IDomainEvent>> handlers;
            Type key = typeof(TDomainEvent);

            if (routes.TryGetValue(key, out handlers))
            {
                handlers.ForEach(act=>act.Invoke(@event));
                appliedEvents.Add(@event);
            }
        }

        public void ClearEvents()
        {
            routes.Clear();
            appliedEvents.Clear();
        }

        private List<Action<IDomainEvent>> GetHandlers<TDomainEvent>()
        {
            List<Action<IDomainEvent>> handlers;
            if (!routes.TryGetValue(typeof(TDomainEvent), out handlers))
            {
                handlers = new List<Action<IDomainEvent>>();
                routes.Add(typeof(TDomainEvent), handlers);
            }
            return handlers;
        }
        protected abstract void RegisterAggregateEvents();

        #endregion

        
    }
}